package com.yanyu.space.file.util;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.google.common.collect.HashMultimap;
import com.yanyu.space.common.core.exception.ServiceException;
import com.yanyu.space.common.core.properties.MinioProperties;
import com.yanyu.space.common.minio.support.CustomMinioClient;
import com.yanyu.space.file.bean.bo.FileShardingInfoBo;
import com.yanyu.space.file.bean.enums.FileRCode;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author yanyu
 */
@Component
@Slf4j
@AllArgsConstructor
public class MinioHelper {

    private final MinioProperties minioProperties;
    private final CustomMinioClient customMinioClient;

    /**
     * 上传文件
     *
     * @param file 上传的文件
     * @return
     */
    public String upload(MultipartFile file, String newFileName) {

        try {
            // 检查存储桶是否已经存在
            boolean isExist = customMinioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
            if (!isExist) {
                log.error("桶不存在 bucketName -> {}", minioProperties.getBucketName());
                throw new ServiceException(FileRCode.FILE_UPLOAD_FAIL);
            }

            String objectName = LocalDateTimeUtil.format(LocalDate.now(), "yyyy/MM/dd") + "/" + newFileName;
            PutObjectArgs.Builder builder = PutObjectArgs.builder().bucket(minioProperties.getBucketName()).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1);
            if (StringUtils.isNoneBlank(file.getContentType())) {
                builder.contentType(file.getContentType());
            }
            PutObjectArgs objectArgs = builder.build();
            //文件名称相同会覆盖
            ObjectWriteResponse objectWriteResponse = customMinioClient.putObject(objectArgs);
            return StringUtils.join(minioProperties.getBucketName(), "/", objectWriteResponse.object());
        } catch (Exception e) {
            throw new ServiceException(FileRCode.FILE_UPLOAD_FAIL, e);
        }
    }

    /**
     * 初始化获取 uploadId
     *
     * @param fileName    文件名
     * @param partCount   分片总数
     * @return
     */
    public FileShardingInfoBo shardingUpload(String fileName, int partCount) {
        try {
            List<String> partUrlList = new ArrayList<>();
            fileName = StringUtils.join(LocalDateTimeUtil.format(LocalDate.now(), "yyyy/MM/dd"), "/", fileName);
            // 获取 uploadId
            String uploadId = customMinioClient.getUploadId(minioProperties.getBucketName(),
                    null,
                    fileName,
                    null,
                    null);
            Map<String, String> paramsMap = new HashMap<>(2);
            paramsMap.put("uploadId", uploadId);
            for (int i = 1; i <= partCount; i++) {
                paramsMap.put("partNumber", String.valueOf(i));
                // 获取上传 url
                String uploadUrl = customMinioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        // 注意此处指定请求方法为 PUT，前端需对应，否则会报 `SignatureDoesNotMatch` 错误
                        .method(Method.PUT)
                        .bucket(minioProperties.getBucketName())
                        .object(fileName)
                        // 指定上传连接有效期
                        .expiry(minioProperties.getShardingUploadExpirySecond(), TimeUnit.SECONDS)
                        .extraQueryParams(paramsMap).build());

                partUrlList.add(uploadUrl);
            }

            // 过期时间
            LocalDateTime expireTime = LocalDateTimeUtil.offset(LocalDateTime.now(), minioProperties.getShardingUploadExpirySecond(), ChronoUnit.SECONDS);
            FileShardingInfoBo result = new FileShardingInfoBo();
            result.setUploadId(uploadId);
            result.setExpiryTime(expireTime);
            result.setUploadUrls(partUrlList);
            result.setFileName(fileName);
            return result;
        } catch (Exception e) {
            throw new ServiceException(FileRCode.FILE_SHARDING_FAIL, e);
        }
    }

    /**
     * 分片合并
     *
     * @param objectName 文件名
     * @param uploadId   uploadId
     * @return
     */
    public String mergeShardingUpload(String objectName, String uploadId) {
        Part[] parts = new Part[1000];
        int partIndex = 0;
        ListPartsResponse partsResponse = listUploadPartsBase(objectName, uploadId);
        if (null == partsResponse) {
            log.error("查询文件分片列表为空");
            throw new ServiceException(FileRCode.FILE_SHARDING_FAIL);
        }
        for (Part partItem : partsResponse.result().partList()) {
            parts[partIndex] = new Part(partIndex + 1, partItem.etag());
            partIndex++;
        }
        ObjectWriteResponse objectWriteResponse;
        try {
            objectWriteResponse = customMinioClient.mergeMultipart(minioProperties.getBucketName(),
                    null, objectName, uploadId, parts, null, null);
            if (null == objectWriteResponse) {
                log.error("合并失败，合并结果为空");
                throw new ServiceException(FileRCode.FILE_MERGE_FAIL);
            }
            return StringUtils.join(minioProperties.getBucketName(), "/", objectWriteResponse.object());
        } catch (Exception e) {
            throw new ServiceException(FileRCode.FILE_MERGE_FAIL, e);
        }
    }

    /**
     * 获取已上传的分片列表
     *
     * @param objectName 文件名
     * @param uploadId   uploadId
     * @return
     */
    public List<Integer> listUploadChunkList(String objectName, String uploadId) {
        ListPartsResponse partsResponse = listUploadPartsBase(objectName, uploadId);
        if (null == partsResponse) {
            return Collections.emptyList();
        }
        return partsResponse.result().partList().stream().map(Part::partNumber).collect(Collectors.toList());
    }

    private ListPartsResponse listUploadPartsBase(String objectName, String uploadId) {
        int maxParts = 1000;
        try {
            ListPartsResponse partsResponse = customMinioClient.listMultipart(minioProperties.getBucketName(), null, objectName, maxParts, 0, uploadId, null, null);
            return partsResponse;
        } catch (Exception e) {
            log.error("查询文件分片列表错误：{}，uploadId:{}", e.getMessage(), uploadId, e);
        }
        return null;
    }
}
